Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Conversation

@Anipik
Copy link

@Anipik Anipik commented Feb 12, 2018

{
throw new ArgumentException(nameof(culture));
}
return new CultureAwareComparer(culture, options);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit newline, per the style above

internal CultureAwareComparer(CultureInfo culture, CompareOptions compareOptions)
{
_compareInfo = culture.CompareInfo;
_compareOptions = compareOptions;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a shame you have to keep _ignoreCase around as it's now redundant, but I agree you can't remove it due to serialization.

@danmoseley
Copy link
Member

This seems reasonable but @tarekgh can you review? There are a forest of various comparers, compareinfo's, cultureinfos and so forth - can you confirm this makes sense.

note this APi was approved here
https://github.com/dotnet/corefx/issues/395#issuecomment-297115553

private readonly CompareInfo _compareInfo; // Do not rename (binary serialization)
private readonly bool _ignoreCase; // Do not rename (binary serialization)
private CompareOptions _compareOptions;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe adding this breaks serialization compat with desktop. It likely needs to be made Optional.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, yes it doesn't implement ISerializable. @ViktorHofer I guess this is the first point we have to consider cross version serialization from 2.0. Should we use [OptionalField] (with a Version?) and have the code check _ignoreCase first? Or switch this to ISerializable (which I assume could be done non breaking, but I don't know.) so we can set it to match _ignoreCase.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also @ViktorHofer do we have a test strategy for 2.0<->2.1 serialization interop?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is one of the [De]Serializ[ing|ed] attributes, but IIRC there was some issue with those that we fixed recently (?)

@Anipik if you're not famliar with binary serialization, read all these pages including this one that is relevant here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, the following would happen:

Core 2.1 --> Core2.0/NETFX: pass, additional field in the blob is ignored
NETFX/Core2.0 --> Core 2.1: throw, missing field in the blob

The easiest solution is as Stephen stated mark _compareOptions as optional. IMO the best solution would be to mark the type as ISerializable, get rid of the _ignoreCase field and during deserialization try to read _compareOptions from the serialization blob with GetValueNoThrow and if that fails read _ignoreCase from the blob. For serialization inside GetObjectData, both fields would needed to be written to the blob.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to clarify, ISerializable is usually the old way used in serialization and it is used more if you are changing the field names. anyway, we need to wait a little bit to tell what is the best way we should go with.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one more thing, OptionalField is very important to have, otherwise will break deserialization on old desktop versions (4.7 for instance)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using it on plenty of types in the BCL, e.g. on all Exception types when we need more control over the serialization process. In this case it makes sense as we can get rid of the extra bool fields which benefits all scenarios, even when you don't care about serialization at all

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can get rid of the extra bool fields which benefits all scenarios

This will break the deserialization on on the full framework. except if you are talking to manually store it when we are serializing it. either way, we have to handle it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I mean to manually store it, that's why I'm pushing for ISerializable.

_compareOptions = compareOptions;
}

private CompareOptions Options
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this property isn't needed. Just passt the _options field to the CompareInfo.Compare calls

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Viktor. this property is not needed


In reply to: 167567810 [](ancestors = 167567810)

return new CultureAwareComparer(culture, ignoreCase);
}

public static StringComparer Create(CultureInfo culture, CompareOptions options)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we introduce this new API I would expect the existing one static StringComparer StringComparer.Create(CultureInfo, bool) to be marked as obsolete.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danmosemsft should I mark this as obsolete ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. it is still ok to use

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not mark new API with the obsolete attribute, because historically it makes it hard for customers with large codebases and "warnings as errors" to upgrade their platforms. Instead you could open an issue in https://github.com/dotnet/platform-compat to flag it dynamically.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I wasn't talking about ObsoleteAttribute but about the compat tooling.

@tarekgh
Copy link
Member

tarekgh commented Feb 12, 2018

please wait don't merge this one.

@tarekgh tarekgh added the * NO MERGE * The PR is not ready for merge yet (see discussion for detailed reasons) label Feb 12, 2018
@tarekgh
Copy link
Member

tarekgh commented Feb 12, 2018

This functionality is already supported in

https://github.com/dotnet/corefx/blob/master/src/System.Globalization.Extensions/src/System/Globalization/Extensions.cs#L12

So you can do it through

CompareInfo.GetStringComparer(options)

We have 2 copies of CultureAwareComparer implementations now. would be better if we think to unify that and keep just one copy in coreclr and change corefx to call the coreclr one.

For serialization, as @stephentoub pointed early this will break the desktop serialization. to fix that, will need to mark the newly introduced field as [OptionalField]

and then add the following method

        [OnDeserialized]
        private void OnDeserializedMethod(StreamingContext context)
        {
            _options |= _ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
        }

@tarekgh
Copy link
Member

tarekgh commented Feb 12, 2018

cc @AlexGhiondea @krwq

return new CultureAwareComparer(culture, ignoreCase);
}

public static StringComparer Create(CultureInfo culture, CompareOptions options)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CompareOptions options [](start = 65, length = 22)

we should validate the options here

}

if (!Enum.IsDefined(typeof(CompareOptions), options))
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should check the options here as we are doing it in https://github.com/dotnet/corefx/blob/master/src/System.Globalization.Extensions/src/System/Globalization/Extensions.cs#L29

the reason is we shouldn't allow ordinal options here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why shouldn't we allow ordinal here ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because if anyone want to use ordinal option should create OrdinalComparer and not culture aware comparer

    private static readonly OrdinalCaseSensitiveComparer s_ordinal = new OrdinalCaseSensitiveComparer();
    private static readonly OrdinalIgnoreCaseComparer s_ordinalIgnoreCase = new OrdinalIgnoreCaseComparer();    

In reply to: 167785331 [](ancestors = 167785331)

private void OnDeserializedMethod(StreamingContext context)
{
_options |= _ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
}
Copy link
Member

@tarekgh tarekgh Feb 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know I have suggested this way but I was looking same time at issue on the full framework. after investigating it I found it is better to have CultureAwareComparer implement ISerializable something like

        public CultureAwareComparer(SerializationInfo info, StreamingContext context)
        {
            _compareInfo = (CompareInfo) info.GetValue("_compareInfo", typeof(CompareInfo));
            bool ignoreCase = info.GetBoolean("_ignoreCase");

            var obj = info.GetValueNoThrow("_options", typeof(CompareOptions));
            if (obj != null)
                _options = (CompareOptions) obj;

            // fix up the _options value in case we are getting old serialized object not having _options
            _options |= ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("_compareInfo", _compareInfo);
            info.AddValue("_options", _options);
            info.AddValue("_ignoreCase", _(options & CompareOptions.IgnoreCase) != 0);
        }

note that you can fully remove _ignoreCase now too.

private readonly CompareInfo _compareInfo; // Do not rename (binary serialization)
private readonly bool _ignoreCase; // Do not rename (binary serialization)

[OptionalField]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the type implements ISerializable and with that takes control of the serialization process, there is no need for an OptionalFieldAttribute here

_options = compareOptions;
}

internal CultureAwareComparer(SerializationInfo info, StreamingContext context)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be private as this constructor will only be invoked by the serialization engine via reflection and because the type is sealed

{
int hashCode = _compareInfo.GetHashCode();
return _ignoreCase ? ~hashCode : hashCode;
return (_options & CompareOptions.IgnoreCase) != 0 ? ~hashCode : hashCode;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hashCode should include all _options, not just ignore case.

[OptionalField]
private CompareOptions _options;

internal CultureAwareComparer(CultureInfo culture, bool ignoreCase)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This constructor is not needed. The callers can use the other one and just pass in the right compare options.

if ((options & CultureAwareComparer.ValidCompareMaskOffFlags) != 0)
{
throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onen last comment: you may move this check to the CultureAwareComparer constructor so you don't have to use ValidCompareMaskOffFlags here and everything will be well contained inside CultureAwareComparer

@tarekgh tarekgh removed the * NO MERGE * The PR is not ready for merge yet (see discussion for detailed reasons) label Feb 13, 2018
@tarekgh
Copy link
Member

tarekgh commented Feb 13, 2018

other than the mentioned comments, LGTM. thanks @Anipik. please don't forget when you expose the new API in the corefx, we'll need to get rid of the duplicate CultureAwareComparer implementation there.

@jkotas
Copy link
Member

jkotas commented Feb 13, 2018

we'll need to get rid of the duplicate CultureAwareComparer implementation there.

That will require moving System.Globalization.GlobalizationExtensions to CoreLib, right?

@tarekgh
Copy link
Member

tarekgh commented Feb 13, 2018

That will require moving System.Globalization.GlobalizationExtensions to CoreLib, right?

No, we don't need to move anything to coreclr, we'll just need to change the implementation of

public static StringComparer GetStringComparer(this CompareInfo compareInfo, CompareOptions options)

to use the newly exposed API.

https://github.com/dotnet/corefx/blob/master/src/System.Globalization.Extensions/src/System/Globalization/Extensions.cs#L12

if (ignoreCase)
return new CultureAwareComparer(culture, CompareOptions.IgnoreCase);

return new CultureAwareComparer(culture, CompareOptions.None);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe you can do it in one line using ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;

private CultureAwareComparer(SerializationInfo info, StreamingContext context)
{
_compareInfo = (CompareInfo)info.GetValue("_compareInfo", typeof(CompareInfo));
bool ignoreCase = info.GetBoolean("ignoreCase");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignoreCase [](start = 47, length = 10)

you are missing the underscore prefix. should be _ignoreCae

@tarekgh
Copy link
Member

tarekgh commented Feb 13, 2018

just to tell we can fully get rid of the implementation in corefx as currently this implementation is used for netfx only and we are not building netfx packages for System.Globalization.Extensions anymore.

Copy link
Member

@ViktorHofer ViktorHofer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes look good to me but before you merge this I want you to verify that this doesn't break compatibility with netfx/netcoreapp2.0. Please add a test in System.Runtime.Seriazation.Formatters.Tests and run it with your local built coreclr package based on this commit.

@Anipik
Copy link
Author

Anipik commented Feb 13, 2018

@dotnet-bot test OSX10.12 x64 Checked Innerloop Build and Test

throw new ArgumentNullException(nameof(culture));
}

return new CultureAwareComparer(culture, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Indentation is one space off.

@jkotas
Copy link
Member

jkotas commented Feb 14, 2018

we can fully get rid of the implementation in corefx as currently this implementation is used for netfx

Note that there is another duplicate implementation at
https://github.com/dotnet/corefx/blob/master/src/System.Runtime.Extensions/src/System/Globalization/Extensions.cs that is used on .NET Core. It would be nice to dedup it with this one...

@jkotas
Copy link
Member

jkotas commented Feb 14, 2018

No, we don't need to move anything to coreclr, we'll just need to change the implementation of
public static StringComparer GetStringComparer(this CompareInfo compareInfo, CompareOptions options)
to use the newly exposed API.

@tarekgh The newly exposed API takes CultureInfo; but this API takes CompareInfo. I do not think there is a good way to map CompareInfo back to CultureInfo.

@tarekgh
Copy link
Member

tarekgh commented Feb 14, 2018

The newly exposed API takes CultureInfo; but this API takes CompareInfo. I do not think there is a good way to map CompareInfo back to CultureInfo.

we don't need the other implementation at all. so we should be fine here. Also we can get the culture object from the compareinfo object using CultureInfo.GetCultureInfo(compareInfo.Name)

@jkotas
Copy link
Member

jkotas commented Feb 14, 2018

Also we can get the culture object from the compareinfo object using CultureInfo.GetCultureInfo(compareInfo.Name)

If GlobalizationExtensions.GetStringComparer starts doing this, it will become order of magnitude slower compare to what it is today...

@tarekgh
Copy link
Member

tarekgh commented Feb 14, 2018

If GlobalizationExtensions.GetStringComparer starts doing this, it will become order of magnitude slower compare to what it is today...

CultureInfo.GetCultureInfo is the cached version which should be good enough. the other idea is to expose override that work with the compareinfo from coreclr too. Also, this extension method is used only for who want to call CompareInfo.GetStringComparer which is not used in all our collection code nor is popular and people should use CultureAwareComparer and this extension.

Copy link
Member

@ViktorHofer ViktorHofer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anirudh verified locally that serialization is still working with netfx/netcoreapp2.0.

@Anipik
Copy link
Author

Anipik commented Feb 14, 2018

@jkotas @tarekgh should I merge it now ?

@tarekgh
Copy link
Member

tarekgh commented Feb 14, 2018

I am fine with the changes

@tarekgh
Copy link
Member

tarekgh commented Feb 15, 2018

@jkotas if you are ok with changes, we can merge this one.

@tarekgh tarekgh merged commit 7995334 into dotnet:master Feb 15, 2018

private readonly CompareInfo _compareInfo; // Do not rename (binary serialization)
private readonly bool _ignoreCase; // Do not rename (binary serialization)
private CompareOptions _options;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that one should be readonly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just checked with net471, they named the field the same. lucky us.
https://referencesource.microsoft.com/#mscorlib/system/stringcomparer.cs,140

@tarekgh
Copy link
Member

tarekgh commented Feb 22, 2018

I just checked with net471, they named the field the same. lucky us.

It is not by luck. we have checked that.

@Anipik Anipik deleted the stringComparer branch March 24, 2018 01:35
{
if (culture == null)
{
throw new ArgumentException(nameof(culture));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this PR is extremely old but why doesn't this throw an ArgumentNullException?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TylerBrinkley I think it is a mistake. Thanks for pointing it out. Generally it's best to open a new issue (or just PR) since posting on closed issues/PR's sometimes gets overlooked.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did somebody follow-up on this? Was an issue created?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created #26570 to address this.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants